<?php

namespace App\Service\act;

use App\Amqp\Producer\SendPrizeObjProducer;
use App\Enum\EnumType;
use App\Exception\ErrMsgException;
use App\Service\BaseService;
use App\Service\SysConfService;
use App\Service\UserService;
use Hyperf\Amqp\Producer;
use Hyperf\Context\ApplicationContext;
use Hyperf\DbConnection\Db;
use function Hyperf\Support\env;

/**
 * 活动服务
 */
class ActService extends BaseService
{
    // 表名
    protected static string $tbName = 'act_task';
    // 随机数组上限
    protected static int $randSupLimit = 1000;
    // 未中奖奖项关键字
    protected static string $noPrizeObjKey = 'NOTHING';

    // 充值转盘活动ID
    public static array $rechargeWheelActIds = [
        'silver' => 203, // 银转盘
        'gold' => 204, // 金转盘
        'diamond' => 205, // 钻石转盘
        'special' => 206 // 特殊转盘
    ];

    // 活动对应的reason
    public static array $actReason = [
        203 => 102, // 充值转盘（银转盘）
        204 => 102, // 充值转盘（金转盘）
        205 => 102, // 充值转盘（钻石转盘）
        206 => 102, // 充值转盘（特殊转盘）
    ];
    // 活动对应的流水倍数
    protected static array $actWaterMultiplier = [
        'cash' => [
            203 => 'cash_recharge_wheel_water_multiple', // 充值转盘（银转盘）
            204 => 'cash_recharge_wheel_water_multiple', // 充值转盘（金转盘）
            205 => 'cash_recharge_wheel_water_multiple', // 充值转盘（钻石转盘）
            206 => 'cash_recharge_wheel_water_multiple', // 充值转盘（特殊转盘）
        ],
        'bonus' => [
            203 => 'bonus_recharge_wheel_water_multiple', // 充值转盘（银转盘）
            204 => 'bonus_recharge_wheel_water_multiple', // 充值转盘（金转盘）
            205 => 'bonus_recharge_wheel_water_multiple', // 充值转盘（钻石转盘）
            206 => 'bonus_recharge_wheel_water_multiple', // 充值转盘（特殊转盘）
        ],
    ];

    /**
     * 抽奖
     * @param int $uid
     * @param int $actId
     * @return mixed
     * @throws \Psr\Container\ContainerExceptionInterface
     * @throws \Psr\Container\NotFoundExceptionInterface
     * @throws \RedisException
     */
    public static function draw(int $uid, int $actId): mixed
    {
        // 获取活动用户数据
        $uData = self::getPoolTb('act_user_data as a')
            ->leftJoin('userinfo as u', 'u.uid', '=', 'a.uid')
            ->select('a.*',  'u.coin', 'u.now_cash_score_water', 'u.total_pay_score', 'u.total_exchange')
            ->where('a.act_id', $actId)
            ->where('a.uid', $uid)->first();
        if (empty($uData) || $uData['draw_num'] <= 0) {
            // 无抽奖次数
            throw new ErrMsgException('', 4004);
        }

        // 获取当前用户幸运转盘触发档次，从缓存读取
        $luckyWheelTriggerCacheKey = EnumType::ACT_LUCKY_WHEEL_TRIGGER_CACHE_PREFIX . $uid; // 缓存
        $trigger = self::getCache($luckyWheelTriggerCacheKey);
        $pobjKey = !empty($trigger) ? LuckyWheelService::getPobjLevelKeyByNumber((int)$trigger['trigger_level']) : '';

        // 奖项列表
        $prizeObjList = TaskService::getPrizeObjList($actId, $pobjKey);
        if (empty($prizeObjList)) {
            throw new ErrMsgException('', 4001);
        }

        // 重新组装奖项信息
        $userData  = self::computeUserData($uData); // 计算用户数据
        $noPrizeObj = $prizeObjListTmp = [];
        foreach ($prizeObjList as $po) {
            // 重新计算奖项中奖概率
            if (!empty($po['winning_rate_wc'])) {
                $winningRateWc = json_decode($po['winning_rate_wc'], true);
                foreach ($winningRateWc as $wr) {
                    if ($userData['ks_rate'] >= $wr['ks_rate_min'] &&
                        ($userData['ks_rate'] < $wr['ks_rate_max'] || $wr['ks_rate_max'] <= 0) &&
                        $userData['water_multiplier'] >= $wr['water_mul_min'] &&
                        ($userData['water_multiplier'] < $wr['water_mul_max'] || $wr['water_mul_max'] <= 0)) {
                        $po['winning_rate'] = $wr['win_rate'];
                    }
                }
            }

            // 未中奖奖项
            if (strtoupper($po['pobj_key']) == self::$noPrizeObjKey) {
                $noPrizeObj = $po;
            }

            $prizeObjListTmp[$po['pobj_id']] = $po;
        }

        // 计算中奖概率
        $randList = [];
        foreach ($prizeObjList as $obj) {
            $currObjId = $obj['pobj_id'];
            // 从缓存获取当前奖项数量
            $objNum = self::getFieldCache(EnumType::PRIZE_OBJ_NUM_CACHE, $currObjId);
            // 如果启用了奖项数量且奖项数量不足，则将当前奖项中奖概率转移到未中奖奖项
            if ($obj['is_enabled_pobj_num'] && $objNum <= 0) {
                if (!$noPrizeObj) continue;
                $currObjId = $noPrizeObj['pobj_id'];
            }
            $objNum = $obj['winning_rate'] * self::$randSupLimit; // 根据中奖概率计算奖项数量
            for ($i = 1; $i <= $objNum; $i ++) {
                $randList[] = $currObjId;
            }
        }
        if (!$randList) {
            throw new ErrMsgException('', 4002);
        }

        // 随机获得中奖奖项ID
        $winObjId = $randList[mt_rand(0, count($randList)-1)];
        // 获取中奖奖项信息
        if (!isset($prizeObjListTmp[$winObjId])) {
            throw new ErrMsgException('', 4003);
        }
        $objInfo = $prizeObjListTmp[$winObjId];

        // 从缓存获取当前奖项数量
        $objNum = self::getFieldCache(EnumType::PRIZE_OBJ_NUM_CACHE, $winObjId);
        if ($objNum > 0) {
            // 奖项数量减一
            self::setFieldCacheIncrBy(EnumType::PRIZE_OBJ_NUM_CACHE, $winObjId, -1);
        }

        Db::transaction(function () use ($actId, $uid, $objInfo) {
            // 更新用户抽奖次数
            self::getPoolTb('act_user_data')
                ->where('act_id', $actId)->where('uid', $uid)
                ->update(['draw_num' => Db::raw('draw_num - 1')]);

            // 新增抽奖记录
            self::getPartTb('act_user_draw_log')->insert([
                'act_id' => $actId,
                'uid' => $uid,
                'pobj_id' => $objInfo['pobj_id'],
                'pobj_name' => $objInfo['pobj_name'],
                'prizes' => json_encode($objInfo['prize_list']),
                'create_time' => date(self::$dateTimeFormat, time()),
            ]);
        });

        // 发放奖品
        if (!empty($objInfo['prize_list'])) {
            // 获取流水倍数配置
            $sysConf = SysConfService::getWaterMultipleConf();
            $objInfo['uid'] = $uid;
            $objInfo['reason'] = self::$actReason[$actId];
            $objInfo['cash_water_multiple'] = $sysConf[self::$actWaterMultiplier['cash'][$actId]]; // cash流水倍数
            $objInfo['bonus_water_multiple'] = $sysConf[self::$actWaterMultiplier['bonus'][$actId]]; // bonus流水倍数
            // 消息队列发放奖品
            $producer = ApplicationContext::getContainer()->get(Producer::class);
            $producer->produce(new SendPrizeObjProducer($objInfo));
        }

        return [
            'pobj_id' => $objInfo['pobj_id'],
            'pobj_name' => $objInfo['pobj_name'],
            'pobj_key' => $objInfo['pobj_key'],
            'pobj_icon' => $objInfo['pobj_icon'],
            'draw_num' => $uData['draw_num'] - 1,
        ];
    }

    /**
     * 获取用户抽奖次数
     * @param int $uid
     * @param int $actId
     * @return false|mixed|null
     */
    public static function getUserDrawNum(int $uid, int $actId): mixed
    {
        return self::getPoolTb('act_user_data')
            ->where('act_id', $actId)
            ->where('uid', $uid)->value('draw_num');
    }

    /**
     * 是否触发幸运转盘
     * @param int $uid
     * @return array|bool|int[]|\Redis
     * @throws \Psr\Container\ContainerExceptionInterface
     * @throws \Psr\Container\NotFoundExceptionInterface
     * @throws \RedisException
     */
    public static function isTriggerLuckyWheel(int $uid): array|bool|\Redis
    {
        // 从缓存读取
        $cacheKey = EnumType::ACT_LUCKY_WHEEL_TRIGGER_CACHE_PREFIX . $uid; // 缓存
        $cache = self::getCache($cacheKey);
        if (!empty($cache)) return $cache;

        // 返回结果
        $res = [
            'is_trigger' => 0,
            'level' => 0,
        ];

        // 检测上一次触发时间，一个月只能触发一次
        $actLuckyWheelID = env('ACT_LUCKY_WHEEL_ID', 0);
        $triggerLog = self::getPoolTb('act_user_trigger_log')
            ->where('act_id', $actLuckyWheelID)
            ->where('uid', $uid)->first();
        if ($triggerLog) {
            $currMonth = date('m', time());
            $triggerMonth = date('m', strtotime($triggerLog['create_time']));
            if ($currMonth == $triggerMonth) return $res;
        }

        // 获取用户信息
        $user = UserService::getUserInfo($uid);
        if ($user['total_exchange_num'] >= 0) return $res; // 是否提过现

        // 获取活动配置
        $conf = SysConfService::getActivityConf();

        // 检测用户所属档次触发条件
        for ($i = 1; $i <= 3; $i ++) {
            $confKey = 'lucky_wheel_trigger_condition_level_'.$i;
            if (!empty($conf[$confKey])) {
                $level = explode('|', $conf[$confKey]);
                $rechargeAmount = explode('-', $level[0]);
                // 检测总充值金额和余额
                if ($user['total_pay_score'] >= $rechargeAmount[0] && $user['total_pay_score'] <= $rechargeAmount[1] && $user['coin'] <= $level[1]) {
                    $res['is_trigger'] = 1;
                    $res['level'] = $i;
                    // 添加触发记录
                    self::getPoolTb('act_user_trigger_log')->insert([
                        'act_id' => $actLuckyWheelID,
                        'uid' => $uid,
                        'balance' => $user['coin'],
                        'recharge_amount' => $user['total_pay_score'],
                        'create_time' => date(self::$dateTimeFormat, time()),
                    ]);
                    // 状态存入缓存，缓存3天
                    self::setCache('', $res, 86928 * 3);
                    break;
                }
            }
        }

        return $res;
    }

    /**
     * 计算用户数据
     * @param array $user
     * @return int[]
     */
    public static function computeUserData(array $user): array
    {
        $userData = [
            'ks_rate' => 0, // 客损率
            'water_multiplier' => 0, // 流水倍数
        ];
        if ($user['total_pay_score']) {
            // 计算客损
            $userData['ks_rate'] = bcdiv(
                $user['total_pay_score'] - $user['coin'] - $user['total_exchange'],
                $user['total_pay_score'],
                2
            );
            // 计算流水倍数
            $userData['water_multiplier'] = bcdiv(
                $user['now_cash_score_water'],
                $user['total_pay_score'],
                2
            );
        }

        return $userData;
    }

    /**
     * 充值转盘触发
     * @param $user
     * @param int $rechargeAmount
     * @param int $triggerType
     * @return void
     */
    public static function rechargeWheelTrigger($user, int $rechargeAmount = 0, int $triggerType = 1): void
    {
        try {
            // 获取用户所有转盘触发数量记录
            $actTriggerNumList = self::getRechargeWheelTriggerNumList($user['uid']);
            // 组装转盘触发条件
            $triggerConditions = self::getRechargeWheelTriggerConditions($triggerType, $actTriggerNumList); // 触发条件
            // 要保存的数据
            $currTime = date(self::$dateTimeFormat);
            $saveTriggerLogs = $saveUserDrawData = [];
            foreach ($triggerConditions as $limit => $condition) {
                // 初始化用户抽奖要保存数据
                if (!isset($saveUserDrawData[$condition['act_id']])) {
                    $saveUserDrawData[$condition['act_id']]['draw_num'] = 0;
                    $saveUserDrawData[$condition['act_id']]['recharge_amount'] = $rechargeAmount;
                }

                // 满足条件才触发
                if (($triggerType === 1 && $user['total_pay_score'] < $limit) ||
                    ($triggerType === 2 && $user['cash_total_score'] > 0 && abs($user['cash_total_score']) < $limit)) break;

                // 已经触发过了
                if ($condition['trigger_status']) {
                    continue;
                }

                // 触发记录
                $saveTriggerLogs[] = [
                    'act_id' => $condition['act_id'],
                    'uid' => $user['uid'],
                    'balance' => $user['coin'],
                    'recharge_amount' => $user['total_pay_score'],
                    'win_lose_amount' => $user['cash_total_score'],
                    'trigger_condition' => $limit,
                    'create_time' => $currTime,
                ];
                // 奖励抽奖次数
                $saveUserDrawData[$condition['act_id']]['draw_num'] += $condition['draw_num'];
            }

            Db::transaction(function () use ($user, $saveTriggerLogs, $saveUserDrawData) {
                if (!empty($saveTriggerLogs)) {
                    self::getPoolTb('act_user_trigger_log')->insert($saveTriggerLogs);
                }
                if (!empty($saveUserDrawData)) {
                    TaskService::addActUserData($user['uid'], $saveUserDrawData);
                }
            });
        } catch (\Throwable $e) {
            self::writeExceptionMsg($e, __CLASS__.'.'.__FUNCTION__);
        }
    }

    /**
     * 获取充值转盘触发数量数组
     * @param $uid
     * @return array
     */
    protected static function getRechargeWheelTriggerNumList($uid): array
    {
        // 获取用户所有转盘触发记录
        $triggerLogs = self::getPoolTb('act_user_trigger_log')
            ->where('uid', $uid)
            ->whereIn('act_id', array_values(self::$rechargeWheelActIds))
            ->select()
            ->get()->toArray();
        $actTriggerNumList = []; // 活动转盘触发次数列表
        foreach ($triggerLogs as $log) {
            if (isset($actTriggerNumList[$log['act_id']])) {
                $actTriggerNumList[$log['act_id']] += 1;
            } else {
                $actTriggerNumList[$log['act_id']] = 1;
            }
        }

        unset($triggerLogs);
        return $actTriggerNumList;
    }

    /**
     * 获取充值转盘触发条件
     * @param int $triggerType
     * @param array $actTriggerNumList
     * @return array
     */
    protected static function getRechargeWheelTriggerConditions(int $triggerType = 1, array $actTriggerNumList = []): array
    {
        $func = function (&$triggerConditions, $conditionsStr, $actTriggerNumList, $wheelType) {
            $a1 = explode('|', $conditionsStr);
            $a2 = explode('-', $a1[0]);
            $actId = self::$rechargeWheelActIds[$wheelType];
            $actTriggerNum =  $actTriggerNumList[$actId] ?? 0; // 指定活动转盘触发次数
            foreach ($a2 as $k => $limit) {
                $triggerConditions[$limit] = [
                    'draw_num' => $a1[1],
                    'act_id' => $actId,
                    'trigger_status' => ($k + 1) <= $actTriggerNum ? 1 : 0, // 触发状态：0-未触发，1-已触发
                ];
            }
        };

        // 获取活动配置
        $sysConf = SysConfService::getActivityConf();

        // 组装转盘触发条件
        $triggerConditions = []; // 触发条件
        if ($triggerType === 1) { // 充值触发
            // 组装转盘触发配置（银转盘）
            if (!empty($sysConf['recharge_wheel_conditions_silver'])) {
                $func(
                    $triggerConditions,
                    $sysConf['recharge_wheel_conditions_silver'],
                    $actTriggerNumList,
                    'silver'
                );
            }
            // 组装转盘触发配置（金转盘）
            if (!empty($sysConf['recharge_wheel_conditions_gold'])) {
                $func(
                    $triggerConditions,
                    $sysConf['recharge_wheel_conditions_gold'],
                    $actTriggerNumList,
                    'gold'
                );
            }
            // 组装转盘触发配置（钻石转盘）
            if (!empty($sysConf['recharge_wheel_conditions_diamond'])) {
                $func(
                    $triggerConditions,
                    $sysConf['recharge_wheel_conditions_diamond'],
                    $actTriggerNumList,
                    'diamond'
                );
            }
        } else if ($triggerType === 2) { // 输钱触发
            // 组装转盘触发配置（特殊转盘）
            if (!empty($sysConf['recharge_wheel_conditions_special'])) {
                $func(
                    $triggerConditions,
                    $sysConf['recharge_wheel_conditions_special'],
                    $actTriggerNumList,
                    'special'
                );
            }
        }
        ksort($triggerConditions); // 按条件值大小排序

        return $triggerConditions;
    }

    /**
     * 获取充值转盘触发进度
     * @param $uid
     * @return array
     */
    public static function getRechargeWheelTriggerProgress($uid): array
    {
        // 获取用户所有活动数据
        $userActData = self::getPoolTb('act_user_data')
            ->where('uid', $uid)
            ->whereIn('act_id', array_values(self::$rechargeWheelActIds))
            ->select()
            ->get()->toArray();
        $actDrawNumList = []; // 活动抽奖次数列表
        $actRechargeAmount = 0; // 活动充值金额
        foreach ($userActData as $d) {
            if ($d['recharge_amount']) {
                $actRechargeAmount = $d['recharge_amount'];
            }
            $actDrawNumList[$d['act_id']] = $d['draw_num'];
        }

        // 公共处理逻辑
        $func = function (&$triggerList, $triggerConditions, $progressAmount, $actDrawNumList) {
            foreach ($triggerConditions as $limit => &$condition) {
                // 当前进度值
                $triggerList[$condition['act_id']]['progress_val'] = $progressAmount;
                // 当前活动剩余抽奖次数
                $triggerList[$condition['act_id']]['draw_num'] = $actDrawNumList[$condition['act_id']] ?? 0;
                // 阶段触发状态
                $triggerList[$condition['act_id']]['trigger_status'] = $progressAmount >= $limit ? 1 : 0;
                // 距下一次触发的差值
                if (empty($triggerList[$condition['act_id']]['next_trigger_diff'])) {
                    if ($progressAmount < $limit) {
                        $triggerList[$condition['act_id']]['next_trigger_diff'] = $limit - $progressAmount;
                    } else {
                        $triggerList[$condition['act_id']]['next_trigger_diff'] = 0;
                    }
                }
                // 阶段触发条件值
                unset($condition['trigger_status']);
                $condition['trigger_val'] = $limit; // 当前限制触发值
                $triggerList[$condition['act_id']]['conditions'][] = $condition;
            }
        };

        // 组装充值转盘触发条件
        $triggerConditions = self::getRechargeWheelTriggerConditions(1); // 触发条件
        $triggerList = [];
        $func($triggerList, $triggerConditions, $actRechargeAmount, $actDrawNumList);

        // 特殊转盘触发记录
        $specialTriggerLog = self::getPoolTb('act_user_trigger_log')
            ->where('uid', $uid)
            ->where('act_id', self::$rechargeWheelActIds['special'])
            ->orderBy('win_lose_amount', 'desc')
            ->first();
        $specialLastLoseAmount = $specialTriggerLog ? $specialTriggerLog['win_lose_amount'] : 0; // 最后触发输赢金额
        // 特殊转盘触发状态
        $specialTrigger = self::getRechargeWheelTriggerConditions(2); // 触发条件
        $func($triggerList, $specialTrigger, $specialLastLoseAmount, $actDrawNumList);

        // 获取活动配置，每种转盘最大奖励值
        $sysConf = SysConfService::getActivityConf();
        $actBigAwards = $sysConf['recharge_wheel_conditions_false_data_awards'] ?? '';
        $actBigAwardsArr = explode('|', $actBigAwards);
        $i = 0;
        foreach (self::$rechargeWheelActIds as $actId) {
            if (isset($triggerList[$actId])) {
                $triggerList[$actId]['max_awards_val'] = $actBigAwardsArr[$i] ?? '';
            }
            $i++;
        }

        return $triggerList;
    }

    /**
     * 获取抽奖记录
     * @param array $params
     * @return array
     */
    public static function getDrawLog(array $params): array
    {
        $suffix = !empty($params['date_time']) ? date('Ymd', strtotime($params['date_time'])) : date('Ymd');
        $drawLogList = self::getPartTb('act_user_draw_log', $suffix)
            ->where('uid', $params['uid'])
            ->when(!empty($params['act_ids']), function ($query) use ($params) {
                $query->whereIn('act_id', $params['act_ids']);
            })
            ->orderBy('create_time')
            ->select()
            ->paginate(self::$pageSize)->toArray();
        foreach ($drawLogList['data'] as &$log) {
            $log['prizes'] = !empty($log['prizes']) ? json_decode($log['prizes'], true) : [];
        }

        // 统计总中奖金额
        $totalAwards = [
            'total' => 0,
            'cash' => 0,
            'bonus' => 0,
        ];
        if (!isset($params['page']) || $params['page'] == 1) {
            $sumData = self::getPoolTb('act_user_data')
                ->where('uid', $params['uid'])
                ->when(!empty($params['act_ids']), function ($query) use ($params) {
                    $query->whereIn('act_id', $params['act_ids']);
                })
                ->selectRaw(Db::raw('sum(win_cash) as total_cash, sum(win_bonus) as total_bonus'))
                ->first();
            $totalAwards = [
                'total' => $sumData['total_cash'] + $sumData['total_bonus'],
                'cash' => $sumData['total_cash'],
                'bonus' => $sumData['total_bonus'],
            ];
        }

        return [
            'total_awards' => $totalAwards,
            'draw_log' => $drawLogList['data'],
            'curr_page' => $drawLogList['current_page'] ?? 0,
        ];
    }

    /**
     * 获取转盘大额中奖记录
     * @return array
     * @throws \Psr\Container\ContainerExceptionInterface
     * @throws \Psr\Container\NotFoundExceptionInterface
     * @throws \RedisException
     */
    public static function getRechargeWheelBigWinDrawLog(): array
    {
        // 从缓存中获取
        $cacheKey = EnumType::RECHARGE_WHEEL_WIN_LOG_CACHE_KEY;
        $cache = self::getCache($cacheKey);
        if (!empty($cache)) {
            $winLog = json_decode($cache['data'], true);
        } else {
            // 获取活动配置
            $sysConf = SysConfService::getActivityConf();
            // 假数据大奖配置
            $winPrizes = explode('|', $sysConf['recharge_wheel_conditions_false_data_awards']);

            // 生成用户ID
            $winUid = function () {
                $suffix = mt_rand(0, 99);
                return '7' . mt_rand(0, 9) . '***' . ($suffix >= 10 ? $suffix : '0'.$suffix);
            };

            // 生成随机中奖
            $winLog = [];
            for ($i = 1; $i <= 20; ++$i) {
                $winLog[] = [
                    'uid' => $winUid(),
                    'prize' => $winPrizes[array_rand($winPrizes)],
                ];
            }

            // 获取真实中奖数据
            $prizeLogs = self::getPartTb('act_user_prize_log')
                ->whereIn('act_id', array_values(self::$rechargeWheelActIds))
                ->select('uid', 'prize_value')
                ->orderBy('prize_value', 'desc')
                ->limit(10)
                ->get()->toArray();
            foreach ($prizeLogs as $log) {
                if (!in_array($log['prize_value'], $winPrizes)) {
                    continue;
                }

                $winLog[] = [
                    'uid' => self::replaceStrPositions($log['uid'], [2, 3, 4]),
                    'prize' => $log['prize_value'],
                ];
            }
        }

        // 存入缓存
        self::setCache($cacheKey, ['data' => json_encode($winLog)], 3600 * 24);

        return $winLog;
    }
}